package com.didiglobal.booster.transform.asm

import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.tree.MethodNode

val MethodNode.args: String
    get() = desc.substring(desc.indexOf('(') + 1, desc.lastIndexOf(')'))

val MethodNode.isAbstract: Boolean
    get() = 0 != (access and Opcodes.ACC_ABSTRACT)

val MethodNode.isPublic: Boolean
    get() = 0 != (access and Opcodes.ACC_PUBLIC)

val MethodNode.isProtected: Boolean
    get() = 0 != (access and Opcodes.ACC_PROTECTED)

val MethodNode.isPrivate: Boolean
    get() = 0 != (access and Opcodes.ACC_PRIVATE)

val MethodNode.isNative: Boolean
    get() = 0 != (access and Opcodes.ACC_NATIVE)

val MethodNode.isStatic: Boolean
    get() = 0 != (access and Opcodes.ACC_STATIC)

val MethodNode.isSynthetic: Boolean
    get() = 0 != (access and Opcodes.ACC_SYNTHETIC)

val MethodNode.isFinal: Boolean
    get() = 0 != (access and Opcodes.ACC_FINAL)

val MethodNode.isConstructor: Boolean
    get() = "<init>" == name && Type.getReturnType(desc) == Type.VOID_TYPE

/**
 * @return true if the method is default access (package-private)
 */
val MethodNode.isDefault: Boolean
    get() = 0 == (access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE))

/**
 * A method is considered accessible to the outer world (including child classes) if:
 *
 * 	- It belongs to a final class; in this case, it can’t be private or default.
 * 	- It belongs to a non-final class; in this case, it can’t be private, protected, or default.
 * 	- It can’t be a synthetic method generated by the compiler.
 * 	- However, for system entry points, protected methods might be accessible. (e.g. Android component lifecycle methods)
 */
val MethodNode.isAccessible: Boolean
    get() = !(isPrivate || isSynthetic || isDefault)

fun MethodNode.isInvisibleAnnotationPresent(vararg annotations: String) = isInvisibleAnnotationPresent(annotations.asIterable())

fun MethodNode.isInvisibleAnnotationPresent(annotations: Iterable<String>) = this.invisibleAnnotations?.map {
    it.desc
}?.any(annotations::contains) ?: false

fun MethodNode.isVisibleAnnotationPresent(vararg annotations: String) = isVisibleAnnotationPresent(annotations.asIterable())

fun MethodNode.isVisibleAnnotationPresent(annotations: Iterable<String>) = this.visibleAnnotations?.map {
    it.desc
}?.any(annotations::contains) ?: false

fun MethodNode.isTheSameAs(other: MethodNode): Boolean {
    return access == other.access && name == other.name && desc == other.desc
}
